package com.easy.secret.utils;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Objects;

import com.easy.secret.codec.AESCodec;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.zz.gmhelper.SM2Util;
import org.zz.gmhelper.cert.SM2CertUtil;

import com.easy.secret.codec.SM4Codec;
import com.easy.secret.config.SecureProperties;

/**
 * @ProjectName: esay-secret-server
 * @Package: com.easy.secret.utils
 * @ClassName: SecureUtils
 * @Description: 加密工具类 - 单例模式
 * @Author: wangjc
 * @CreateDate: 2023/11/17
 * @UpdateUser: wangjc
 * @UpdateDate: 2023/11/17
 * @UpdateRemark: 更新内容
 * @Version: 1.0
 */
@Slf4j
public class SecureUtils {

    /**
     * 秘钥
     */
    private byte[] key;

    /**
     * sm4加密类
     */
    private SM4Codec sm4Codec;

    private AESCodec aecCodec;

    /**
     * 单例
     */
    private static SecureUtils instance;

    private SecureProperties properties;

    private SecureUtils(){
    }

    /**
     * 初始化
     * @param properties
     * @return
     */
    private SecureUtils init(SecureProperties properties) {
        this.properties = properties;
        switch (properties.getAlgorithm()) {
            case "aes" : {
                try {
                    aecCodec = AESCodec.instance(this.getKey(), properties.getIvString().getBytes(), properties.getToStringType());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            }
            case "sm4" :
            default: {
                String sk = new String(this.getKey(), StandardCharsets.UTF_8);
                sk = Objects.nonNull(sk)&&sk.length()>16 ? sk.substring(16) : sk;

                sm4Codec = new SM4Codec(sk, properties.getIvString(), null, null, properties.getToStringType());
                break;
            }
        }

        return this;
    }

    /**
     * 获取当前工具栏的实例
     *
     * @param properties    配置文件
     * @return
     */
    public static SecureUtils getInstance(SecureProperties properties) {
        if(Objects.isNull(instance)) {
            synchronized (SecureUtils.class) {
                if(Objects.isNull(instance)){
                    instance = new SecureUtils().init(properties);
                }
            }
        }
        return instance;
    }

    /**
     * 加密字符串 - 日期加密
     *
     * @param date
     * @return
     */
    public String encryptDate(Date date) {
        if(Objects.isNull(date)){
            return null;
        }
        String dateString = date.getTime() + "";
        return this.doEncrypt(dateString);
    }

    /**
     * 加密字符串
     * @param encryptData
     * @return
     */
    public String encrypt(String encryptData) {
        return this.doEncrypt(encryptData);
    }

    private String doEncrypt(String encryptData){
        String encStr = null;
        try {
            encStr = sm4Codec.encrypt(encryptData);

            switch (properties.getAlgorithm()) {
                case "aes" : {
                    encStr = aecCodec.decode(encryptData);
                    break;
                }
                case "sm4" :
                default: {
                    encStr = sm4Codec.encrypt(encryptData);
                    break;
                }
            }
        } catch (Exception e) {
            log.error("加密字符串异常：{}", e.getMessage());
            encStr = encryptData;
        }
        return encStr;
    }

    /**
     * 解密字符串 - 日期类型解密
     * @param decryptData
     * @return
     */
    public Date decryptDate(String decryptData) {
        String dateString = this.doDecrypt(decryptData);
        return new Date(NumberUtils.toLong(dateString));
    }

    /**
     * 解密字符串
     * @param decryptData
     * @return
     */
    public String decrypt(String decryptData) {
        return doDecrypt(decryptData);
    }

    private String doDecrypt(String decryptData) {
        String decStr = null;
        try{
            switch (properties.getAlgorithm()) {
                case "aes" : {
                    decStr = aecCodec.decode(decryptData);
                    break;
                }
                case "sm4" :
                default: {
                    decStr = sm4Codec.decrypt(decryptData);
                    break;
                }
            }
        } catch (Exception e) {
            log.error("解密字符串异常：{}", e.getMessage());
            decStr = decryptData;
        }
        return decStr;
    }

    /**
     * 获取加密秘钥 sm4秘钥
     * @return
     */
    private byte[] getKey(){
        if(Objects.isNull(key)) {
            try{
                String url = properties.getSm4KeyUrl() + "?accessToken="
                        + properties.getAccessToken() + "&compartId="
                        + properties.getCompartId();
                String sm4KeyEncryptString = get(url);
                if(Objects.isNull(sm4KeyEncryptString)){
                    log.error("secret server is not available!");
                }
                BCECPrivateKey privateKey = SM2CertUtil.getPrivateKeyFromPfx(RandomFileUtil.readFile(properties.getPfxFileName()), this.properties.getPfxPasswd());
                this.key = SM2Util.decrypt(privateKey, ByteUtils.fromHexString(sm4KeyEncryptString));
            } catch(Exception e){
                log.error("secret server get key exception:{}", e.getMessage());
            }
        }
        if(Objects.isNull(this.key)){
            log.error("secret server response keys is null!");
        }

        return this.key;
    }

    /**
     * 去加密机获取秘钥
     *
     * @param requestUrl
     * @return
     */
    public String get(String requestUrl) {
        String result = null;
        HttpURLConnection connection = null;
        InputStream is = null;
        BufferedReader br = null;
        try {
            URL url = new URL(requestUrl);
            connection = (HttpURLConnection)url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(15000);
            connection.setReadTimeout(60000);
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            connection.connect();
            if (connection.getResponseCode() == 200) {
                is = connection.getInputStream();
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                StringBuffer sbf = new StringBuffer();
                String line = null;

                while((line = br.readLine()) != null) {
                    sbf.append(line);
                    sbf.append("\r\n");
                }

                result = sbf.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != br) {
                    br.close();
                }
                if (null != is) {
                    is.close();
                }
            } catch (Exception var17) {
                var17.printStackTrace();
            }
            connection.disconnect();
        }

        return result;
    }
}
